﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Threading;
using System.Net;
using System.Net.Sockets;


namespace NetworkingLibrary.Servers.UDP
{
    public interface IUDPServer : IServer
    {
        IPAddress LocalAddress
        {
            get;
        }
        bool Start(int Port, string Addr, int? MaximumConnections = null, IPAddress MulticastAddress = null, int MulticastTTL = 0);

        Utilities.UDP.UDPMessagingMode UDPMode
        {
            get;
            set;
        }
    }

    public class UDPServer : IUDPServer
    {
        private int maxFlowRate = 5;
        public int MaxFlowRate
        {
            get
            {
                return maxFlowRate;
            }
            set
            {
                maxFlowRate = value;
            }
        }
        DateTime lastSendTime = new DateTime();
        public DateTime LastSendTime
        {
            get
            {
                return lastSendTime;
            }
            set
            {
                lastSendTime = value;
            }
        }



        private int bufferSize = 65000;
        public int BufferSize
        {
            get
            {
                return bufferSize;
            }
            set
            {
                bufferSize = value;
            }
        }
        bool initialised = false;
        public bool Initialised
        {
            get
            {
                return initialised;
            }
        }
        bool terminating = false;
        public bool Terminating
        {
            get
            {
                return terminating;
            }
            set
            {
                if(value)
                {
                    terminating = value;
                }
            }
        }

        //Events have to be overriden so that Invoke can be called on them
        public virtual event Events.ClientConnectDelegate OnClientConnect = delegate { };
        public virtual event Events.ClientDisconnectDelegate OnClientDisconnect = delegate { };
        public virtual event Events.MaxConnectionsDelegate OnMaxConnections = delegate { };
        public virtual event Events.MessageReceivedDelegate OnMessageReceived = delegate { };
        public virtual event Events.ErrorDelegate OnError = delegate { };

        private Dictionary<Guid, Clients.EmptyClient> allClients = new Dictionary<Guid, Clients.EmptyClient>();
        public Dictionary<Guid, Clients.IClient> AllClients
        {
            get
            {
                return allClients.ToDictionary(x => x.Key, x => x.Value as Clients.IClient);
            }
        }

        public IPAddress LocalAddress
        {
            get
            {
                return Dns.GetHostAddresses(Dns.GetHostName()).First();
            }
        }

        private int maxConnections = 10;
        public int MaxConnections
        {
            get
            {
                return maxConnections;
            }
            set
            {
                maxConnections = value;
            }
        }

        private Clients.UDP.UDPClient TheListener;

        public Utilities.UDP.UDPMessagingMode UDPMode
        {
            get
            {
                return TheListener.UDPMode;
            }
            set
            {
                TheListener.UDPMode = value;
            }
        }
        
        public UDPServer()
        {
        }
        public UDPServer(Events.MaxConnectionsDelegate OnMaxConnectionsReached, Events.MessageReceivedDelegate OnAMessageReceived)
        {
            OnMaxConnections += OnMaxConnectionsReached;
            OnMessageReceived += OnAMessageReceived;
        }

        public bool Initialise(int Port, string Addr)
        {
            try
            {
                TheListener = new Clients.UDP.UDPClient(Port);
                TheListener.OnMessageReceived += new Events.MessageReceivedDelegate(TheListener_OnMessageReceived);
                return true;
            }
            catch (Exception e)
            {
                OnError.Invoke(new Events.ErrorEventArgs(e));
            }
            return false;
        }

        private void TheListener_OnMessageReceived(Events.MessageReceivedEventArgs e)
        {
            try
            {
                if (e.TheMessage != null)
                {
                    OnMessageReceived.Invoke(e);
                    if (!e.PreventDefault)
                    {
                        Type MessageType = e.TheMessage.GetType();
                        if (MessageType == typeof(Messages.UDP.Requests.ConnectRequestMessage))
                        {
                            Messages.UDP.Requests.ConnectRequestMessage TheMessage = (Messages.UDP.Requests.ConnectRequestMessage)e.TheMessage;

                            if (allClients.Count < MaxConnections)
                            {
                                Clients.EmptyClient NewClient = new Clients.EmptyClient();
                                NewClient.Port = TheMessage.Port;
                                NewClient.Address = TheMessage.Address;
                                NewClient.ID = Guid.NewGuid();
                                allClients.Add(NewClient.ID, NewClient);

                                if (SendTo(new Messages.UDP.Responses.ConnectResponseMessage(true, NewClient.ID), NewClient.ID))
                                {
                                    OnClientConnect.Invoke(new Events.ClientConnectEventArgs(NewClient, allClients.Count));

                                    if (allClients.Count >= MaxConnections)
                                    {
                                        OnMaxConnections.Invoke(new Events.MaxConnectionsEventArgs(MaxConnections));
                                    }
                                }
                            }
                            else
                            {
                                Clients.EmptyClient NewClient = new Clients.EmptyClient();
                                NewClient.Port = TheMessage.Port;
                                NewClient.Address = TheMessage.Address;                                
                                DoSend(new Messages.UDP.Responses.ConnectResponseMessage(false, NewClient.ID), new Clients.EmptyClient[] { NewClient }.ToList());
                            }
                        }
                        else if (MessageType == typeof(Messages.UDP.DisconnectMessage))
                        {
                            Messages.UDP.DisconnectMessage TheMessage = (Messages.UDP.DisconnectMessage)e.TheMessage;
                            OnClientDisconnect.Invoke(new Events.ClientDisconnectEventArgs(allClients[TheMessage.ClientID], allClients.Count));
                            allClients.Remove(TheMessage.ClientID);
                        }
                        else
                        {
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
        }

        public bool Start(int Port, string Addr, int? MaximumConnections = null)
        {
            return Start(Port, Addr, MaximumConnections, null, 0);
        }
        public bool Start(int Port, string Addr, int? MaximumConnections = null, IPAddress MulticastAddress = null, int MulticastTTL = 0)
        {
            try
            {
                terminating = false;

                bool OK = true;

                if (!Initialised)
                {
                    OK = initialised = Initialise(Port, Addr);
                }

                if (OK)
                {
                    MaxConnections = MaximumConnections.HasValue ? MaximumConnections.Value : MaxConnections;
                    if (MulticastAddress != null)
                    {
                        TheListener.Connect(Port, MulticastAddress, true, true, MulticastTTL);
                    }
                    else
                    {
                        TheListener.Start();
                    }
                }

                return OK;
            }
            catch (Exception e)
            {
                OnError.Invoke(new Events.ErrorEventArgs(e));
            }
            return false;
        }
        public bool Stop()
        {
            try
            {
                if (Initialised)
                {
                    Terminating = true;

                    if (TheListener.Stop())
                    {
                        TheListener = null;
                        initialised = false;
                        return true;
                    }
                }
                else
                {
                    return true;
                }
            }
            catch (Exception e)
            {
                OnError.Invoke(new Events.ErrorEventArgs(e));
            }
            return false;
        }
        
        public bool Send(byte[] TheBuffer)
        {
            try
            {
                return Send(new Messages.DataMessage(TheBuffer));
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool Send(byte[] TheBuffer, Guid ExcludeID)
        {
            try
            {
                return Send(new Messages.DataMessage(TheBuffer), ExcludeID);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool SendTo(byte[] TheBuffer, Guid ToID)
        {
            try
            {
                return SendTo(new Messages.DataMessage(TheBuffer), ToID);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool Send(Messages.MessageObject TheMessage)
        {
            try
            {
                return DoSend(TheMessage, allClients.Values.Cast<Clients.EmptyClient>().ToList());
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool Send(Messages.MessageObject TheMessage, Guid ExcludeID)
        {
            try
            {
                return DoSend(TheMessage, (from PotClients in allClients
                                           where (PotClients.Key != ExcludeID)
                                           select PotClients.Value).Cast<Clients.EmptyClient>().ToList());
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool SendTo(Messages.MessageObject TheMessage, Guid ToId)
        {
            try
            {
                if (allClients.ContainsKey(ToId))
                {
                    return DoSend(TheMessage, new Clients.EmptyClient[] { allClients[ToId] }.ToList());
                }
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        private bool DoSend(Messages.MessageObject TheMessage, List<Clients.EmptyClient> TheClients)
        {
            try
            {
                bool OK = true;

                TheClients.ForEach(delegate(Clients.EmptyClient TheClient)
                {
                    OK = TheListener.SendTo(TheClient.Address, TheClient.Port, TheMessage);
                });

                return OK;
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }

        public void DisconnectExcessClients()
        {
            try
            {
                while (allClients.Count > MaxConnections)
                {
                    Guid TheAddress = allClients.Last().Key;
                    Clients.EmptyClient TheClient = allClients[TheAddress];
                    
                    if (TheClient != null)
                    {
                        if (SendTo(new Messages.UDP.DisconnectMessage(TheClient.ID), TheClient.ID))
                        {
                            allClients.Remove(TheAddress);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
        }
    }
}
